{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Entanglement Distillation -- BBPSSW Protocol\n",
    "\n",
    "\n",
    "<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "Quantum entanglement plays a vital role in quantum communication, quantum computing, and many other quantum technologies. Therefore, detecting, transmitting, and distributing quantum entanglement reliably are essential tasks if we want to build real applications in those fields. However, errors are inevitable in the real world. They could come from imperfect equipment when we create entanglement (preparation errors), or the quantum channel used to transmit entanglement is noisy, and we gradually lose the degree of entanglement as the transmission distance increases. The aim of entanglement distillation is to compensate for those losses and restore a **maximally entangled state** at the cost of many noisy entangled states. In this sense, one could also refer entanglement distillation as a purification/error-correction protocol. This process often involves two remote parties Alice and Bob such that only Local Operations and Classical Communication (LOCC) are allowed [1]. The concept of entanglement distillation was first introduced by Bennett et al. [2] in 1996 and the original protocol is known as the **BBPSSW protocol** following the name of the authors."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### The Bell states\n",
    "\n",
    "Many protocols in quantum information processing utilize the Bell states, including quantum teleportation [3], superdense coding [4] and quantum cryptography based on Bell’s theorem [5]. Such an enormous application range makes the four bell states an important subject of study. They are usually defined in the following bi-partite notation,\n",
    "\n",
    "$$ \n",
    "\\begin{align*}\n",
    "|\\Phi^{\\pm}\\rangle_{AB} &= \\frac{1}{\\sqrt{2}}(|0\\rangle_A\\otimes|0\\rangle_B \\pm |1\\rangle_A\\otimes|1\\rangle_B), \\\\\n",
    "|\\Psi^{\\pm}\\rangle_{AB} &= \\frac{1}{\\sqrt{2}}(|0\\rangle_A\\otimes|1\\rangle_B \\pm |1\\rangle_A\\otimes|0\\rangle_B). \n",
    "\\tag{1}\n",
    "\\end{align*}\n",
    "$$\n",
    "\n",
    "where $A$ (Alice) holds one qubit and $B$ (Bob) holds the other qubit. They could be physically separated. These states can not be decomposed as a single product state $|\\varphi\\rangle_A\\otimes |\\psi\\rangle_B$. **Note: This is a key feature of entangled pure states**. \n",
    "\n",
    "There is an interesting feature about Bell state that if trace out subsystem $A$, the reduced density operator $\\rho_B$ will become $I/2$. For example, let's take a look at the Bell state $|\\Phi^{+}\\rangle_{AB}$\n",
    "\n",
    "$$\n",
    "\\begin{align*}\n",
    "\\rho_B &\\equiv \\text{tr}_A(\\Phi^{+}_{AB}), \\\\\n",
    "& = \\text{tr}_A(|\\Phi^{+}\\rangle\\langle\\Phi^+|_{AB}), \\\\\n",
    "& = \\frac{1}{2}\\text{tr}_A\\big(|00\\rangle\\langle00|+|00\\rangle\\langle11|+|11\\rangle\\langle00|+|11\\rangle\\langle11|\\big),\\\\\n",
    "& = \\frac{1}{2} \\big(\\text{tr}_A(|0\\rangle\\langle0|_A)\\cdot|0\\rangle\\langle0|_B + \\text{tr}_A(|0\\rangle\\langle1|_A)\\cdot|0\\rangle\\langle1|_B  \\\\\n",
    "& \\quad\\quad + \\text{tr}_A(|1\\rangle\\langle0|_A) \\cdot|1\\rangle\\langle0|_B+\n",
    "\\text{tr}_A(|1\\rangle\\langle1|_A)\\cdot|1\\rangle\\langle1|_B \\big), \\\\\n",
    "& = \\frac{1}{2} (|0\\rangle\\langle0|_B + |1\\rangle\\langle1|_B) = \\frac{1}{2} I.\n",
    "\\tag{2}\n",
    "\\end{align*}\n",
    "$$\n",
    "\n",
    "There exists a non-classical correlation between the entangled qubit pair. We cannot describe the whole system with side information about the sub-systems. One can create the Bell states $\\Phi^{+}_{AB}$ (density matrix) easily in Paddle Quantum by calling the function `bell_state()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:37.924098Z",
     "start_time": "2021-02-23T09:10:37.671533Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The generated state Phi  = \n",
      " [[0.5+0.j 0. +0.j 0. +0.j 0.5+0.j]\n",
      " [0. +0.j 0. +0.j 0. +0.j 0. +0.j]\n",
      " [0. +0.j 0. +0.j 0. +0.j 0. +0.j]\n",
      " [0.5+0.j 0. +0.j 0. +0.j 0.5+0.j]]\n"
     ]
    }
   ],
   "source": [
    "from paddle_quantum.state import bell_state\n",
    "N = 2 # number of qubits\n",
    "rho = bell_state(N)\n",
    "print(\"The generated state Phi  = \\n\", rho)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Entanglement quantification\n",
    "\n",
    "After having a taste of quantum entanglement qualitatively, we want to promote our understanding to a quantitive level. One should realize the validity of aforementioned quantum communication protocols, including quantum teleportation and superdense coding, depends on **the quality of the entanglement quantification**. Following the convention in quantum information community, the **negativity** $\\mathcal{N}(\\rho)$ and the **logarithmic negativity** $E_{N}(\\rho)$ are widely recognized metrics to quantify the amount of entanglement presented in a bi-partite system [6]. Specifically,\n",
    "\n",
    "$$\n",
    "\\mathcal{N}(\\rho) \\equiv \\frac{||\\rho^{T_B}||_1-1}{2},\n",
    "\\tag{3}\n",
    "$$\n",
    "\n",
    "and\n",
    "\n",
    "$$\n",
    "E_{N}(\\rho) \\equiv \\text{log}_2 ||\\rho^{T_B}||_1,\n",
    "\\tag{4}\n",
    "$$\n",
    "\n",
    "where $T_B$ stands for the partial transpose operation with respect to subsystem $B$ and can be defined as [7],\n",
    "\n",
    "$$\n",
    "\\rho_{A B}^{T_{B}}:=(I \\otimes T)(\\rho_{A B})=\\sum_{i, j}(I_{A} \\otimes|i\\rangle\\langle j|_{B}) \\rho_{A B}(I_{A} \\otimes|i\\rangle\\langle j|_{B}).\n",
    "\\tag{5}\n",
    "$$\n",
    "\n",
    "\n",
    "$||\\cdot||_1$ denotes the trace norm and $\\{|i\\rangle_B\\}$ is a basis of subsystem $B$. It is easy to connect these two metrics as $E_{N}(\\rho) = \\text{log}_2 (2\\mathcal{N}(\\rho)+1)$. They are all related to the PPT criterion for separability. \n",
    "\n",
    "> *A density matrix has a positive partial transpose (PPT) if its partial transposition has no negative eigenvalues (i.e. positive semidefinite). Otherwise, it's called NPT.*\n",
    "\n",
    "The reason why we introduce such metrics and criterion is not only helping us better understand quantum entanglement but also guiding us in distillation protocols. **Note: All PPT states are NOT distillable**. This means we can not use many copies of the same PPT states to reach a state closer to the maximally entangled state through LOCC in the asymptotic regime. So, it is always a good idea to check the PPT condition before we put the initial state into distillation protocols. For a detailed discussion on the relation between the positive map theory and separability, please refer to this review paper [8]. All these concepts are coded in Paddle Quantum by calling the functions `negativity()`,`logarithmic_negativity()`, and `partial_transpose()`.\n",
    "\n",
    "\n",
    "\n",
    "After introducing many new concepts, we provide a concrete example. For the specific Bell state $|\\Phi^{+}\\rangle_{AB}$, we have\n",
    "\n",
    "$$\n",
    "\\begin{align*}\n",
    "\\rho^{T_{B}} &= \\frac{1}{2}\n",
    "\\big(|00\\rangle\\langle00|+|01\\rangle\\langle10|+|10\\rangle\\langle01|+|11\\rangle\\langle11|\\big),\\\\\n",
    "& = \\frac{1}{2}\n",
    "\\begin{bmatrix}\n",
    "1 & 0 & 0 & 0\\\\\n",
    "0 & 0 & 1 & 0\\\\\n",
    "0 & 1 & 0 & 0\\\\\n",
    "0 & 0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\tag{6}\n",
    "\\end{align*}\n",
    "$$\n",
    "\n",
    "with a negative eigenvalue $\\lambda_{-}(\\rho^{T_{B}}) = -1/2$ and logarithmic negativity $E_{N}(\\rho) = 1$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:43.024928Z",
     "start_time": "2021-02-23T09:10:41.024088Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The transposed state is\n",
      " [[0.5+0.j 0. +0.j 0. +0.j 0. +0.j]\n",
      " [0. +0.j 0. +0.j 0.5+0.j 0. +0.j]\n",
      " [0. +0.j 0.5+0.j 0. +0.j 0. +0.j]\n",
      " [0. +0.j 0. +0.j 0. +0.j 0.5+0.j]]\n",
      "With negativity = 0.5 and logarithmic_negativity = 1.0\n",
      "The input state satisfies the PPT condition and hence not distillable? False\n"
     ]
    }
   ],
   "source": [
    "from paddle_quantum.state import bell_state\n",
    "from paddle_quantum.utils import negativity, logarithmic_negativity, partial_transpose, is_ppt\n",
    "\n",
    "input_state = bell_state(2)\n",
    "transposed_state = partial_transpose(input_state, 2)   # 2 stands for qubit numbers\n",
    "print(\"The transposed state is\\n\", transposed_state)\n",
    "\n",
    "neg = negativity(input_state)\n",
    "log_neg = logarithmic_negativity(input_state)\n",
    "print(\"With negativity =\", neg,\"and logarithmic_negativity =\", log_neg)\n",
    "print(\"The input state satisfies the PPT condition and hence not distillable?\", is_ppt(input_state))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the context of entanglement distillation, we usually use the **state fidelity** $F$ between the distilled state $\\rho_{out}$ and the Bell state $|\\Phi^+\\rangle$ to quantify the performance of a distillation protocol, where\n",
    "\n",
    "$$\n",
    "F(\\rho_{out}, \\Phi^+) \\equiv \\langle \\Phi^+|\\rho_{out}|\\Phi^+\\rangle.\n",
    "\\tag{7}\n",
    "$$\n",
    "\n",
    "Recall the target state in distillation task is $|\\Phi^+\\rangle$. It is natural to choose state fidelity as the metric to quantify the distance between the current state and the target state. To calculate state fidelity, we can call the function `state_fidelity(rho, sigma)`.\n",
    "\n",
    "$$\n",
    "F_s(\\rho,\\sigma) \\equiv \\text{tr}\\big( \\sqrt{\\sqrt{\\rho}\\sigma \\sqrt{\\rho}} \\big).\n",
    "\\tag{8}\n",
    "$$\n",
    "\n",
    "Notice they are differed by a square root $F=F_s^2$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:45.384306Z",
     "start_time": "2021-02-23T09:10:45.376793Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The state fidelity between these two states are:\n",
      "F= 0.7750000000000004\n"
     ]
    }
   ],
   "source": [
    "from paddle_quantum.state import bell_state, isotropic_state\n",
    "from paddle_quantum.utils import state_fidelity\n",
    "\n",
    "F = state_fidelity(bell_state(2), isotropic_state(2, 0.7))\n",
    "print(\"The state fidelity between these two states are:\\nF=\", F**2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With many preparations, we can finally discuss the famous BBPSSW protocol in the next section."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## BBPSSW protocol\n",
    "\n",
    "The BBPSSW protocol distills two identical quantum states $\\rho_{in}$ (2-copies) into a single final state $\\rho_{out}$ with a higher state fidelity $F$ (closer to the Bell state $|\\Phi^+\\rangle$). It is worth noting that BBPSSW was mainly designed to purify **isotropic states** (also known as Werner state), a parametrized family of mixed states consist of $|\\Phi^+\\rangle$ and the completely mixed state (white noise) $I/4$.\n",
    "\n",
    "$$\n",
    "\\rho_{\\text{iso}}(p) = p\\lvert\\Phi^+\\rangle \\langle\\Phi^+\\rvert + (1-p)\\frac{I}{4}, \\quad p \\in [0,1]\n",
    "\\tag{9}\n",
    "$$\n",
    "\n",
    "This state is PPT and hence not distillable when $p \\leq 1/3$. \n",
    "\n",
    "We have two remote parties, $A$ (Alice) and $B$ (Bob), involved in the BBPSSW distillation protocol. They share two qubit pairs $\\rho_{A_0B_0}$ and $\\rho_{A_1B_1}$. Each pair is initialized as $\\rho_{in} = \\rho_{\\text{iso}}(p)$. These states could come from last distillation process or previously distributed and stored in some memory registers of a quantum network. Alice holds two qubits $A_0, A_1$, and Bob also holds two qubits $B_0, B_1$. With these initial setups, Alice and Bob implements the following LOCC process:\n",
    "\n",
    "1. Alice and Bob firstly choose the qubit pair **they want to keep as the memory qubit pair to store entanglement resource after distillation**. Here, they choose $A_0$ and $B_0$. \n",
    "2. Alice and Bob both apply a CNOT gate on their qubits. Here, they choose $A_0,B_0$ as the control qubits and $A_1,B_1$ as the target qubits.\n",
    "3. Two remote parties measure the target qubits then use classical communication channel to exchange their measurement results $m_{A_1}, m_{B_1}$.\n",
    "4. If the measurement results of Alice and Bob are the same (00 or 11), the distillation is successful, and the qubit pair $A_0, B_0$ is stored as state $\\rho_{out}$; if the measurement results are different (01 or 10), they claim the distillation failed and the qubit pair $A_0, B_0$ will be discarded.\n",
    "\n",
    "<center><img src=\"figures/distillation-fig-BBPSSW.png\" height=\"200\" width=\"400\"></center>\n",
    "<div style=\"text-align:center\">Figure 1: Schematic diagram of the BBPSSW protocol </div>\n",
    "\n",
    "\n",
    "**Note:** The distillation protocol is probabilistic and it does not succeed every time. So, another metric to quantify the performance of distillation protocol is **the probability of success $p_{succ}$**. In general, there exists a trade off between the final state fidelity $F' = F(\\rho_{out},\\Phi^+)$ and $p_{succ}$. Theoretically, these two specific metrics for the BBPSSW protocol are provided as,\n",
    "\n",
    "\n",
    "$$\n",
    "F^{\\prime}=\\frac{F^2 + \\frac{1}{9}(1-F)^2}{F^2 + \\frac{2}{3}F(1-F) + \\frac{5}{9}(1-F)^2},\n",
    "\\tag{10}\n",
    "$$\n",
    "\n",
    "where the initial fidelity $F(\\rho_{in},\\Phi^+) = (1+3p)/4$ with a probability of success,\n",
    "\n",
    "$$\n",
    "p_{\\text {succ }}=F^{2} + \\frac{2}{3}F(1-F) + \\frac{5}{9}(1-F)^2.\n",
    "\\tag{11}\n",
    "$$\n",
    "\n",
    "In the next section, we will go through how to simulate the BBPSSW protocol with Paddle Quantum.\n",
    "\n",
    "**Note:** If the input state is not an isotropic state, one can depolarize it to an isotropic state with a twirling operation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simulation with Paddle Quantum \n",
    "\n",
    "First, we need to import all the dependencies:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:48.714566Z",
     "start_time": "2021-02-23T09:10:48.322765Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from paddle_quantum.locc import LoccNet\n",
    "from paddle import matmul, trace\n",
    "import paddle\n",
    "from paddle_quantum.state import bell_state, isotropic_state\n",
    "from paddle_quantum.utils import logarithmic_negativity, is_ppt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For convenience, we introduce the metrics of BBPSSW here and later compare with the simulation result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:50.880991Z",
     "start_time": "2021-02-23T09:10:50.861653Z"
    }
   },
   "outputs": [],
   "source": [
    "def BBPSSW_metrics(p):\n",
    "    \"\"\"\n",
    "    Returns output fidelity and probability of success of the BBPSSW protocol.\n",
    "    \"\"\"\n",
    "    F_in = (1+3*p)/4\n",
    "    p_succ = F_in**2 + 2*F_in*(1-F_in)/3+ 5*(1-F_in)**2/9\n",
    "    F_out = (F_in**2 + (1-F_in)**2/9)/p_succ\n",
    " \n",
    "    return F_in, F_out, p_succ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In our example, the input state is an isotropic state with $p=0.7$, that is\n",
    "\n",
    "$$\n",
    "\\rho_{in} = \\rho_{\\text{iso}}(0.7)= 0.7\\lvert\\Phi^+\\rangle \\langle\\Phi^+\\rvert + 0.075 I.\n",
    "\\tag{12}\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:52.180240Z",
     "start_time": "2021-02-23T09:10:52.174381Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The input fidelity is: 0.7749999999999999\n",
      "The output fidelity is: 0.8137583892617447\n",
      "With a probability of success: 0.745\n",
      "The input state satisfies the PPT condition and hence not distillable? False\n"
     ]
    }
   ],
   "source": [
    "p = 0.7\n",
    "F_in, F_out, p_succ = BBPSSW_metrics(p)\n",
    "print(\"The input fidelity is:\", F_in)\n",
    "print(\"The output fidelity is:\", F_out)\n",
    "print(\"With a probability of success:\", p_succ)\n",
    "print(\"The input state satisfies the PPT condition and hence not distillable?\", is_ppt(isotropic_state(2, p)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can simulate the BBPSSW protocol and check if the results match with theory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:53.985975Z",
     "start_time": "2021-02-23T09:10:53.973998Z"
    },
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "class LOCC(LoccNet):\n",
    "    def __init__(self):\n",
    "        super(LOCC, self).__init__()\n",
    "        \n",
    "        # Add the first party Alice \n",
    "        # The first parameter 2 stands for how many qubits A holds\n",
    "        # The second parameter records the name of this qubit holder\n",
    "        self.add_new_party(2, party_name='Alice')\n",
    "        \n",
    "        # Add the second party Bob\n",
    "        # The first parameter 2 stands for how many qubits B holds\n",
    "        # The second parameter records the name of this qubit holder\n",
    "        self.add_new_party(2, party_name='Bob')\n",
    "        \n",
    "        # Define the input quantum states rho_in\n",
    "        _state = paddle.to_tensor(isotropic_state(2, p))\n",
    "        \n",
    "        # ('Alice', 0) means Alice's first qubit A0\n",
    "        # ('Bob', 0) means Bob's first qubit B0\n",
    "        self.set_init_state(_state, [('Alice', 0), ('Bob', 0)]) \n",
    "        \n",
    "        # ('Alice', 1) means Alice's second qubit A1\n",
    "        # ('Bob', 1) means Bob's second qubit B1\n",
    "        self.set_init_state(_state, [('Alice', 1), ('Bob', 1)])  \n",
    "        \n",
    "    def BBPSSW(self):\n",
    "        status = self.init_status\n",
    "        \n",
    "        # Create Alice's local operations \n",
    "        cir1 = self.create_ansatz('Alice')\n",
    "        cir1.cnot([0, 1])\n",
    "\n",
    "        # Create Bob's local operations \n",
    "        cir2 = self.create_ansatz('Bob')\n",
    "        cir2.cnot([0, 1])\n",
    "    \n",
    "        # Run circuit\n",
    "        status = cir1.run(status)\n",
    "        status_mid = cir2.run(status)\n",
    "        \n",
    "        # ('Alice', 1) means measuring Alice's second qubit A1\n",
    "        # ('Bob', 1) means measuring Bob's second qubit B1\n",
    "        # ['00','11'] specifies the success condition for distillation\n",
    "        # Means Alice and Bob both measure '00' or '11'\n",
    "        status_mid = self.measure(status_mid, [('Alice', 1), ('Bob', 1)], [\"00\", \"11\"])\n",
    "        \n",
    "        # Trace out the measured qubits A1&B1\n",
    "        # Leaving only Alice’s first qubit and Bob’s first qubit A0&B0 as the memory register\n",
    "        status_fin = self.partial_state(status_mid, [('Alice', 0), ('Bob', 0)])\n",
    "        \n",
    "        return status_fin"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-02-23T09:10:58.444882Z",
     "start_time": "2021-02-23T09:10:58.274024Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The fidelity of the input quantum state is: 0.77500\n",
      "The fidelity of the purified quantum state is: 0.81376\n",
      "The probability of successful purification is: 74.500%\n",
      "========================================================\n",
      "The output state is:\n",
      " [[0.48489933+0.j 0.        +0.j 0.        +0.j 0.32885906+0.j]\n",
      " [0.        +0.j 0.01510067+0.j 0.        +0.j 0.        +0.j]\n",
      " [0.        +0.j 0.        +0.j 0.01510067+0.j 0.        +0.j]\n",
      " [0.32885906+0.j 0.        +0.j 0.        +0.j 0.48489933+0.j]]\n",
      "The initial logarithmic negativity is: 0.6322682154995127\n",
      "The final logarithmic negativity is: 0.7026724166123284\n"
     ]
    }
   ],
   "source": [
    "# Run BBPSSW protocol\n",
    "status_fin = LOCC().BBPSSW()\n",
    "\n",
    "# Calculate fidelity\n",
    "target_state = paddle.to_tensor(bell_state(2))\n",
    "fidelity = 0\n",
    "for status in status_fin:\n",
    "    fidelity += paddle.real(trace(matmul(target_state, status.state)))\n",
    "fidelity /= len(status_fin)\n",
    "\n",
    "# Calculate success rate\n",
    "suc_rate = sum([status.prob for status in status_fin])\n",
    "fidelity_in,_,_ = BBPSSW_metrics(p)\n",
    "\n",
    "# Output simulation results\n",
    "print(f\"The fidelity of the input quantum state is: {fidelity_in:.5f}\")\n",
    "print(f\"The fidelity of the purified quantum state is: {fidelity.numpy()[0]:.5f}\")\n",
    "print(f\"The probability of successful purification is: {suc_rate.numpy()[0]:#.3%}\")\n",
    "\n",
    "# Print the output state\n",
    "rho_out = status_fin[0].state.numpy()\n",
    "print(\"========================================================\")\n",
    "print(f\"The output state is:\\n {rho_out}\")\n",
    "print(f\"The initial logarithmic negativity is: {logarithmic_negativity(isotropic_state(2,p))}\")\n",
    "print(f\"The final logarithmic negativity is: {logarithmic_negativity(rho_out)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "As we can see, the simulation results matches the theoretical prediction perfectly. Through the BBPSSW protocol, we can extract an entangled state with higher Fidelity from two entangled states with low Fidelity.\n",
    "\n",
    "**Advantages**\n",
    "- Operations are simple. Only CNOT gates are required.\n",
    "- High success rate.\n",
    "\n",
    "**Disadvantages**\n",
    "- Strict requirements for the input state type. Isotropic states are preferred.\n",
    "- Poor scalability. Unable to directly extend to the case of multiple copies.\n",
    "\n",
    "We suggest interested readers to check the tutorial on [how to design a new distillation protocol with LOCCNet](./EntanglementDistillation_LOCCNet_EN.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## References\n",
    "\n",
    "[1] Chitambar, Eric, et al. \"Everything you always wanted to know about LOCC (but were afraid to ask).\" [Communications in Mathematical Physics 328.1 (2014): 303-326.](https://link.springer.com/article/10.1007/s00220-014-1953-9)\n",
    "\n",
    "[2] Bennett, Charles H., et al. \"Purification of noisy entanglement and faithful teleportation via noisy channels.\" [Physical Review Letters 76.5 (1996): 722.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.76.722)\n",
    "\n",
    "[3] Bennett, Charles H., et al. \"Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels.\" [Physical Review Letters 70.13 (1993): 1895.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.70.1895)\n",
    "\n",
    "[4] Bennett, Charles H., and Stephen J. Wiesner. \"Communication via one-and two-particle operators on Einstein-Podolsky-Rosen states.\" [Physical Review Letters 69.20 (1992): 2881.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.69.2881)\n",
    "\n",
    "[5] Ekert, Artur K. \"Quantum cryptography based on Bell’s theorem.\" [Physical Review Letters 67.6 (1991): 661.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.67.661)\n",
    "\n",
    "[6] Życzkowski, Karol, et al. \"Volume of the set of separable states.\" [Physical Review A 58.2 (1998): 883.](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.58.883)\n",
    "\n",
    "[7] Peres, Asher. \"Separability criterion for density matrices.\" [Physical Review Letters 77.8 (1996): 1413.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.77.1413)\n",
    "\n",
    "[8] Gühne, Otfried, and Géza Tóth. \"Entanglement detection.\" [Physics Reports 474.1-6 (2009): 1-75.](https://www.sciencedirect.com/science/article/abs/pii/S0370157309000623)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
